<?php

namespace Xin\Hashing;

use RuntimeException;
use Xin\Hashing\Contracts\Hasher as HasherContract;

class BcryptHasher extends AbstractHasher implements HasherContract
{

	/**
	 * The default cost factor.
	 *
	 * @var int
	 */
	protected $rounds = 10;

	/**
	 * Indicates whether to perform an algorithm check.
	 *
	 * @var bool
	 */
	protected $verifyAlgorithm = false;

	/**
	 * Create a new hasher instance.
	 *
	 * @param array $options
	 * @return void
	 */
	public function __construct(array $options = [])
	{
		$this->rounds = $options['rounds'] ?? $this->rounds;
		$this->verifyAlgorithm = $options['verify'] ?? $this->verifyAlgorithm;
	}

	/**
	 * Hash the given value.
	 *
	 * @param string $value
	 * @param array $options
	 * @return string
	 * @throws \RuntimeException
	 */
	public function make($value, array $options = [])
	{
		$hash = password_hash($value, PASSWORD_BCRYPT, [
			'cost' => $this->cost($options),
		]);

		if ($hash === false) {
			throw new RuntimeException('Bcrypt hashing not supported.');
		}

		return $hash;
	}

	/**
	 * Check the given plain value against a hash.
	 *
	 * @param string $value
	 * @param string $hashedValue
	 * @param array $options
	 * @return bool
	 * @throws \RuntimeException
	 */
	public function check($value, $hashedValue, array $options = [])
	{
		if ($this->verifyAlgorithm && $this->info($hashedValue)['algoName'] !== 'bcrypt') {
			throw new RuntimeException('This password does not use the Bcrypt algorithm.');
		}

		return parent::check($value, $hashedValue, $options);
	}

	/**
	 * Check if the given hash has been hashed using the given options.
	 *
	 * @param string $hashedValue
	 * @param array $options
	 * @return bool
	 */
	public function needsRehash($hashedValue, array $options = [])
	{
		return password_needs_rehash($hashedValue, PASSWORD_BCRYPT, [
			'cost' => $this->cost($options),
		]);
	}

	/**
	 * Set the default password work factor.
	 *
	 * @param int $rounds
	 * @return $this
	 */
	public function setRounds($rounds)
	{
		$this->rounds = (int)$rounds;

		return $this;
	}

	/**
	 * Extract the cost value from the options array.
	 *
	 * @param array $options
	 * @return int
	 */
	protected function cost(array $options = [])
	{
		return $options['rounds'] ?? $this->rounds;
	}

}
